package cn.xinfei.xdecision.data.core.trace;

import cn.xinfei.xdecision.common.model.datax.consts.VariableConst;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataException;
import cn.xinfei.xdecision.common.model.datax.exception.XDecisionDataExceptionType;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.UUID;

@Slf4j
@Aspect
@Component
public class TraceAspect {


    @Pointcut("@annotation(cn.xinfei.xdecision.data.core.trace.VarTrace)")
    public void request() {
    }

    @Before("request()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        Object target = joinPoint.getTarget();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        try {
            Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
            VarTrace varTrace = method.getAnnotation(VarTrace.class);
            VarTrace.TraceScene traceScene = varTrace.traceScene();
            if (varTrace.traceEnabled()) {
                String traceId = UUID.randomUUID().toString().trim().replaceAll("-", "") + "_" + traceScene.getCode();
                if (StringUtils.isBlank(MDC.get(VariableConst.TRACE_ID))) {
                    MDC.put(VariableConst.TRACE_ID, traceId);
                }

                log.info("URL                   : {}", request.getRequestURL().toString());
                log.info("HTTP METHOD           : {}", request.getMethod());
                log.info("Class Method          : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
                log.info("Request Args          : {}", JSON.toJSON(joinPoint.getArgs()));
            }

        } catch (Exception ex) {
            log.error("error in doBefore at VarTrace = {}", ex.getMessage(), ex);
        }
    }

    @Around("request()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String traceId = MDC.get(VariableConst.TRACE_ID);
        log.info("traceId = {}============ Start ========================", traceId);
        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            Object target = joinPoint.getTarget();
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            result = joinPoint.proceed();
            Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
            VarTrace varTrace = method.getAnnotation(VarTrace.class);
            VarTrace.TraceScene traceScene = varTrace.traceScene();
            long duration = System.currentTimeMillis() - startTime;
            if (varTrace.traceEnabled()) {
                switch (traceScene) {
                    case PRE_COLLECT:
                        log.info("[PRE_COLLECT] traceId:{} Time-Consuming:{}", traceId, duration);
                        break;
                    case CREDIT_FETCH:
                        log.info("[CREDIT_FETCH] traceId:{} Time-Consuming:{}", traceId, duration);
                        break;
                    case REAL_COLLECT:
                        log.info("[REAL_COLLECT] traceId:{} Time-Consuming:{}", traceId, duration);
                        break;
                    case CREDIT_UPLOAD:
                        log.info("[CREDIT_UPLOAD] traceId:{} Time-Consuming:{}", traceId, duration);
                        break;
                    default:
                        throw new XDecisionDataException(XDecisionDataExceptionType.COMMON_CLASS_CAST_EXCEPTION);
                }
            }
            log.info("Response Args         : {}", JSON.toJSON(result));
            log.info("Time-Consuming        : {} ms", System.currentTimeMillis() - startTime);
            log.info("traceId = {}============= End =========================", MDC.get(VariableConst.TRACE_ID));
        } catch (Exception ex) {
            log.error("error in doAround at VarTrace  cause = {}", ex.getMessage(), ex);
        } finally {
            MDC.clear();
        }
        return result;
    }

}
